package com.wdl.datarest.implementation;

import java.io.IOException;
import java.net.DatagramSocket;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.UnknownHostException;

/**
 * Device simulator for generating heartbeat/breathe_rate data and alarm.
 */
public class DeviceSim {

    private String hostName;
    private int port;
    DatagramSocket socketClient;
    
    public DeviceSim(String hostName, int port){
        this.hostName = hostName;
        this.port = port;
    }

    public void sendDeviceData(byte[] buf) throws UnknownHostException, IOException{
        
        System.out.println("DeviceSim to send: ");
        for (int i = 0; i < buf.length; i++) {
            if (i < buf.length -1) {
                System.out.print(String.format("%02X ",buf[i]));
            } else {
                System.out.println(String.format("%02X",buf[i]));
            }
        }
        
        //Get a datagram socket
        socketClient = new DatagramSocket();

        //Send data
        //byte[] buf = new byte[128];
        //buf = deviceData.getBytes();
        
        InetAddress address = InetAddress.getByName(hostName);
        DatagramPacket packet = new DatagramPacket(buf, buf.length, address, port);
        socketClient.send(packet);
        
        for (int j=0; j<101; j++) {
            
            //Get response
            byte[] suData = new byte[521];
            packet = new DatagramPacket(suData, suData.length);
            System.out.println("Response from server: ");
            socketClient.setSoTimeout(300000); // 30s
            socketClient.receive(packet);
    
            //Show response
            byte[] byteData = packet.getData();
            int pktLength = 521;
            if(byteData[1] == (byte)0xF4) {
                pktLength = 10;
            }
            //for (int i = 0; i < pktLength; i++) {
            for (int i = 0; i < 20; i++) {
                //if (i < pktLength -1) {
                if (i < 20 -1) {
                    System.out.print(String.format("%02X ",byteData[i]));
                } else {
                    System.out.println(String.format("%02X",byteData[i]));
                }
            }
            
            if (byteData[1] == (byte)0xF4) {
                break;
            }
            System.out.println("Device software frame num locals: " + (j+1) + ", frame num received in packet: " + byteData[2]);
        }
     
        socketClient.close();
    }

    private static void printUsage() {
        System.out.println("\n");
        System.out.println("Usage: DeviceSim <start_dev_id> <end_dev_id> <alarm_type> <num_packets>");
        System.out.println("          start_dev_id should be less than or equal to end_dev_id");
        System.out.println("          dev_id should between 1 ~ 100000 (including 1 and 100000)");
        System.out.println("          The alarm_type can be: ");
        System.out.println("             heart - A1, breathe - A2, normal - A0");
        System.out.println("             situp - C1, edge - C2, away - C3, lie - C4");
        System.out.println("             move - C5, wet - C6, leftturnover - C8, rightturnover - C9, clear - 30");
        System.out.println("             ring - C7 , cancel - C0");
        System.out.println("             all kinds of alarm rotating one by one - FF");
        System.out.println("             move/wet/sit/turnover alarms at the same - EE");
        System.out.println("             heart/breathe alarms at the same - DD");
        System.out.println("             handshake - CC");
        System.out.println("             software update - AA");
        System.out.println("          num_packets is a positive number for how many to send(use 0 for SU)");
        System.out.println("          To simulate one device, give same value to start_dev_id and end_dev_id");
        System.out.println("\n");
        System.exit(1);
    }
    
    private static short getCrc16(byte[] Msg, int length) {
        int CRC = 0xffff;
        int wCPoly = 0x1021;
        int wChar = 0;
        int i = 0;

        while (i < length){

            wChar = Msg[i];
            CRC ^= (wChar << 8);

            for (int count = 0; count < 8; count++) {
                if ((CRC & 0x8000) != 0) {
                    CRC = (CRC << 1) ^ wCPoly;
                } else {
                    CRC = CRC << 1;
                }
            }
            i++;
        }
        //System.out.println("The CRC is: " + String.format("%X", CRC));
        return (short)CRC;
    }

    private static byte[] short2bytes(short s) {
        byte[] bytes = new byte[2];
        for (int i = 0; i < 2; i++) {
            bytes[i] = (byte)(s % 256);
            s >>= 8;
        }
        return bytes;
    }
    
    public static void main(String arg[]){
        if(arg.length != 5) {
            System.out.println("\n");
            System.out.println("Wrong number of parameters.");
            printUsage();
        }
        long startDevId = Long.parseLong(arg[0]);
        long endDevId = Long.parseLong(arg[1]);
        byte alarmType = (byte)Integer.parseInt(arg[2], 16);
        long maxPackets = Long.parseLong(arg[3]);
        
        System.out.println("startDevId=" + startDevId + ", endDevId=" + endDevId + 
                ", alarmType=0x" + String.format("%02X ", alarmType) + ", maxPackets=" + maxPackets);
        if(startDevId > 100000 || startDevId <= 0) {
            System.out.println("Invalid start_dev_id: " + startDevId);
            printUsage();
        }
        if(endDevId > 100000 || endDevId <= 0) {
            System.out.println("Invalid end_dev_id: " + endDevId);
            printUsage();
        }
        if(startDevId > endDevId) {
            System.out.println("start_dev_id(" + startDevId + ") should be less than or equal to end_dev_id(" + endDevId +")");
            printUsage();
        }
        
        //DeviceSim client = new DeviceSim ("localhost",10166);
        // DeviceSim client = new DeviceSim ("47.105.79.216",10166);
        DeviceSim client = new DeviceSim (arg[4],10166);
        
        byte[] bytePacket = new byte[25];

        // For testing software update
        if((alarmType == (byte)0xAA) && (maxPackets == 0)) {
            if (startDevId != endDevId) {
                System.out.println("For device SU testing please make sure startDevId same as endDevId.");
                printUsage();
            }
            bytePacket[0] = (byte)0xE2; bytePacket[1] = (byte)0xDA; bytePacket[2] = (byte)1; bytePacket[3] = (byte)0xE;
            bytePacket[4] = 0x30; bytePacket[5] = 0x35; bytePacket[6] = 0x43; bytePacket[7] = 0x48;
            bytePacket[14] = 0x1; bytePacket[15] = 0x0; bytePacket[16] = 0x0; bytePacket[17] = 0x0; 
            
            long tmpDeviceId = startDevId;
            for (int i = 0; i < 6; i++) {
                bytePacket[8+i] = (byte)(tmpDeviceId/Math.pow(10, 5-i) + 0x30);
                tmpDeviceId = (long)(tmpDeviceId%Math.pow(10, 5-i));
            }
            
            byte[] byteCRCMsg = new byte[17];
            for (int i= 0; i< byteCRCMsg.length; i++){
                byteCRCMsg[i] = bytePacket[i+1];
            }
            short shortCRC = getCrc16(byteCRCMsg, byteCRCMsg.length);
            byte[] byteCRC = short2bytes(shortCRC);
            bytePacket[18] = byteCRC[1]; bytePacket[19] = byteCRC[0]; bytePacket[20] = (byte)0xE3;
            
            try {
                client.sendDeviceData(bytePacket);
            } catch (UnknownHostException e) {
                System.err.println("Host unknown. Cannot establish connection");
            } catch (IOException e) {
                System.err.println("Cannot establish connection. Server may not be up. "+e.getMessage());
            } catch (Exception e){
                System.err.println("Unkonwn exception");
            }
            
            bytePacket[0] = (byte)0xE2; bytePacket[1] = (byte)0xDB; bytePacket[2] = (byte)2; bytePacket[3] = (byte)0xB;
            bytePacket[4] = 0x30; bytePacket[5] = 0x35; bytePacket[6] = 0x43; bytePacket[7] = 0x48;
            bytePacket[14] = 0x1;
            bytePacket[18] = 0; bytePacket[19] = 0; bytePacket[20] = 0;
            
            byte[] byteCRCMsg1 = new byte[14];
            for (int i= 0; i< byteCRCMsg1.length; i++){
                byteCRCMsg1[i] = bytePacket[i+1];
            }
            shortCRC = getCrc16(byteCRCMsg1, byteCRCMsg1.length);
            byteCRC = short2bytes(shortCRC);
            bytePacket[15] = byteCRC[1]; bytePacket[16] = byteCRC[0]; bytePacket[17] = (byte)0xE3;
            
            try {
                client.sendDeviceData(bytePacket);
            } catch (UnknownHostException e) {
                System.err.println("Host unknown. Cannot establish connection");
            } catch (IOException e) {
                System.err.println("Cannot establish connection. Server may not be up."+e.getMessage());
            } catch (Exception e){
                System.err.println("Unkonwn exception");
            }
            
            return;
        }
        
        long devId = startDevId;
        short fNum = 1;
        long numPackets = 0;

        //byte sleepMode = (byte)0xB1;        
        byte bedPosition = (byte)0xC3;
        byte turnOver = (byte)0xC8;

        byte heartBeat = 40; // normal scope 60 ~ 100 for adult
        byte breatheRate = 10; // normal scope 16 ~ 20 for adult
        
        while(true){
            if(numPackets >= maxPackets) {
                System.out.println("Done sending " + maxPackets + " packets.");
                break;
            }
            numPackets++;
            
            if (bedPosition == (byte)0xC7) {
                bedPosition = (byte)0xC1;
            }
            
            if (turnOver == (byte)0xC8) {
                turnOver = (byte)0xC9;
            } else if (turnOver == (byte)0xC9) {
                turnOver = (byte)0xC8;
            }
            
            if (heartBeat > (byte)120){
                heartBeat = (byte)40;
            }
            
            if(breatheRate > (byte)25){
                breatheRate = (byte)10;
            }
                                    
            long tmpDevId = devId;
            for (int i = 0; i < 6; i++) {
                bytePacket[8+i] = (byte)(tmpDevId/Math.pow(10, 5-i) + 0x30);
                tmpDevId = (long)(tmpDevId%Math.pow(10, 5-i));
            }
            
            // Send move/wet alarm every 1000 packets.
            if (alarmType == (byte)0xCC) {
                bytePacket[0] = (byte)0xE2; bytePacket[1] = (byte)0xCC; bytePacket[2] = (byte)fNum; bytePacket[3] = (byte)0xA;
                bytePacket[4] = 0x30; bytePacket[5] = 0x31; bytePacket[6] = 0x43; bytePacket[7] = 0x48; 
                
                byte[] byteCRCMsg = new byte[13];
                for (int i= 0; i< byteCRCMsg.length; i++){
                    byteCRCMsg[i] = bytePacket[i+1];
                }
                short shortCRC = getCrc16(byteCRCMsg, byteCRCMsg.length);
                byte[] byteCRC = short2bytes(shortCRC);
                bytePacket[14] = byteCRC[1]; bytePacket[15] = byteCRC[0]; bytePacket[16] = (byte)0xE3;            
            } else if ((alarmType == (byte)0xC0) || (alarmType == (byte)0xC1) || (alarmType == (byte)0xC2) || 
                    (alarmType == (byte)0xC3) || (alarmType == (byte)0xC4) || (alarmType == (byte)0xC5) ||
                    (alarmType == (byte)0xC6) || (alarmType == (byte)0xC7) || (alarmType == (byte)0x30) || 
                    (alarmType == (byte)0xC8) || (alarmType == (byte)0xC9) ||
                    ((alarmType == (byte)0xFF) && (numPackets%1000 == 0))) {
                // 0xBB packet
                bytePacket[0] = (byte)0xE2; bytePacket[1] = (byte)0xBB; bytePacket[2] = (byte)fNum; bytePacket[3] = (byte)0xE;
                bytePacket[4] = 0x30; bytePacket[5] = 0x31; bytePacket[6] = 0x43; bytePacket[7] = 0x48;   
                bytePacket[14] = (byte)0xC4; bytePacket[15] = (byte)0x30; bytePacket[16] = (byte)0x30; bytePacket[17] = (byte)0x30;
                
                if(alarmType == (byte)0xC0 || (alarmType == (byte)0xC1) || (alarmType == (byte)0xC2) || 
                        (alarmType == (byte)0xC3) || (alarmType == (byte)0xC4) || (alarmType == (byte)0xC7)) {
                    bytePacket[14] = (byte)alarmType;
                }
                
                if((alarmType == (byte)0xC5) || (alarmType == (byte)0x30)) {
                    bytePacket[15] = (byte)alarmType;
                }

                if((alarmType == (byte)0xC6) || (alarmType == (byte)0x30)) {
                    bytePacket[16] = (byte)alarmType;
                }
                
                if((alarmType == (byte)0xC8) || (alarmType == (byte)0xC9) || (alarmType == (byte)0x30)) {
                    bytePacket[17] = (byte)alarmType;
                }
                
                if((alarmType == (byte)0xFF) && (numPackets % 1000 == 0)) {
                    bytePacket[14] = (byte)bedPosition;
                    bytePacket[15] = (byte)0xC5;
                    bytePacket[16] = (byte)0xC6;
                    bytePacket[17] = (byte)turnOver;
                    bedPosition++;
                }
                
                byte[] byteCRCMsg = new byte[17];
                for (int i= 0; i< byteCRCMsg.length; i++){
                    byteCRCMsg[i] = bytePacket[i+1];
                }
                short shortCRC = getCrc16(byteCRCMsg, byteCRCMsg.length);
                byte[] byteCRC = short2bytes(shortCRC);
                bytePacket[18] = byteCRC[1]; bytePacket[19] = byteCRC[0]; bytePacket[20] = (byte)0xE3;
            } else if (alarmType == (byte)0xEE) {
                // 0xBB packet
                bytePacket[0] = (byte)0xE2; bytePacket[1] = (byte)0xBB; bytePacket[2] = (byte)fNum; bytePacket[3] = (byte)0xE;
                bytePacket[4] = 0x30; bytePacket[5] = 0x31; bytePacket[6] = 0x43; bytePacket[7] = 0x48;   
                bytePacket[14] = (byte)0xC1; bytePacket[15] = (byte)0xC5; bytePacket[16] = (byte)0xC6; bytePacket[17] = (byte)0xC8;    
                
                byte[] byteCRCMsg = new byte[17];
                for (int i= 0; i< byteCRCMsg.length; i++){
                    byteCRCMsg[i] = bytePacket[i+1];
                }
                short shortCRC = getCrc16(byteCRCMsg, byteCRCMsg.length);
                byte[] byteCRC = short2bytes(shortCRC);
                bytePacket[18] = byteCRC[1]; bytePacket[19] = byteCRC[0]; bytePacket[20] = (byte)0xE3;
            } else if (alarmType == (byte)0xDD) {
                // 0xAA packet
                bytePacket[0] = (byte)0xE2; bytePacket[1] = (byte)0xAA; bytePacket[2] = (byte)fNum; bytePacket[3] = (byte)0xF;
                bytePacket[4] = 0x30; bytePacket[5] = 0x31; bytePacket[6] = 0x43; bytePacket[7] = 0x48;   
                bytePacket[14] = (byte)heartBeat; bytePacket[15] = (byte)breatheRate; bytePacket[16] = (byte)(fNum%(byte)3 + 0xB1);
                bytePacket[17] = (byte)0xA1; bytePacket[18] = (byte)0xA2;
                
                byte[] byteCRCMsg = new byte[18];
                for (int i= 0; i< byteCRCMsg.length; i++){
                    byteCRCMsg[i] = bytePacket[i+1];
                }
                short shortCRC = getCrc16(byteCRCMsg, byteCRCMsg.length);
                byte[] byteCRC = short2bytes(shortCRC);
                bytePacket[19] = byteCRC[1]; bytePacket[20] = byteCRC[0]; bytePacket[21] = (byte)0xE3;
            } else {
                // 0xAA packet
                bytePacket[0] = (byte)0xE2; bytePacket[1] = (byte)0xAA; bytePacket[2] = (byte)fNum; bytePacket[3] = (byte)0xF;
                bytePacket[4] = 0x30; bytePacket[5] = 0x31; bytePacket[6] = 0x43; bytePacket[7] = 0x48;   
                bytePacket[14] = (byte)heartBeat; bytePacket[15] = (byte)breatheRate; bytePacket[16] = (byte)(fNum%(byte)3 + 0xB1);
                bytePacket[17] = (byte)0xA0; bytePacket[18] = (byte)0xA0;
                
                if ((alarmType == (byte)0xA1) || (alarmType == (byte)0xA0)) {
                    bytePacket[17] = (byte)alarmType;
                }
                
                if ((alarmType == (byte)0xA2) || (alarmType == (byte)0xA0)) {
                    bytePacket[18] = (byte)alarmType;
                }
                
                if((alarmType == (byte)0xFF) && (numPackets%1500 == 0)) {
                    //if(heartBeat < 60 || heartBeat > 100) {
                        bytePacket[17] = (byte)0xA1;
                    //}
                    //if(breatheRate < 16 || breatheRate > 20) {
                        bytePacket[18] = (byte)0xA2;
                    //}
                }
                
                heartBeat++;
                breatheRate++;
                
                byte[] byteCRCMsg = new byte[18];
                for (int i= 0; i< byteCRCMsg.length; i++){
                    byteCRCMsg[i] = bytePacket[i+1];
                }
                short shortCRC = getCrc16(byteCRCMsg, byteCRCMsg.length);
                byte[] byteCRC = short2bytes(shortCRC);
                bytePacket[19] = byteCRC[1]; bytePacket[20] = byteCRC[0]; bytePacket[21] = (byte)0xE3;
            }
            
            System.out.println("devId=" + devId);
            /*
            System.out.println("devId=" + devId + ", bytePacket[8]=" + String.format("%02X ", bytePacket[8]) +
                    ", bytePacket[9]=" + String.format("%02X ", bytePacket[9]) +
                    ", bytePacket[10]=" + String.format("%02X ", bytePacket[10]) +
                    ", bytePacket[11]=" + String.format("%02X ", bytePacket[11]) +
                    ", bytePacket[12]=" + String.format("%02X ", bytePacket[12]) +
                    ", bytePacket[13]=" + String.format("%02X ", bytePacket[13]));
            */
            
            try {
                client.sendDeviceData(bytePacket);
                // To support 10000 devices. Each device 1data/5seconds. So 10000data/5s = 2000data/s
                // 1s/10ms = 100 means 100data/second, so need 20 simulators simultaneously. 
                Thread.sleep(10); 
            } catch (UnknownHostException e) {
                System.err.println("Host unknown. Cannot establish connection");
            } catch (IOException e) {
                System.err.println("Cannot establish connection. Server may not be up."+e.getMessage());
            } catch (Exception e){
                System.err.println("Unkonwn exception");
            }
            
            if(devId++ == endDevId) {
                devId = startDevId;
                
                // Prepare for next data
                if(fNum++ == (byte)255) {
                    fNum = 1;
                }
            }
        }
    }
}